/*
   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License version 2 as published by the Free Software Foundation.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; see the file COPYING.LIB.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110-1301, USA.
*/

#ifndef IFUNCTION_H
#define IFUNCTION_H
/// qt
#include <QString> 
#include <QDebug>
///eigen ? 
#include <Eigen/Core>

/// std
#include <memory>
///mu 
#include "muparser/muParser.h"
///mine
#include "parserfactory.h"
#include <QStringList>
///boost
#include <boost/scoped_array.hpp>

///TODO: add exceptions

namespace Math{
    /*!
     * \brief Enumeration over types of function avaible. 
     *          Function can exist in only three states
     */ 
    enum FunctionDef {
        eUnknown=0, /*!< Unknown. When function does not have equation, and no data has been assign */
        eDiscrete,/*!< Discrete. When function is in fact in Matrix form. */
        eContinuous /*!< Continuous. When function is given by equation, so matrix is not used*/
    };
    /*!
     * \brief Enumeration over methods for calculing derivative
     *          Function can exist in only three states
     */
    enum DerivativeMethod {
        eFiniteDifferenceTwoPoints=0,
        eFiniteDifferenceThreePoints,
        eFiniteDifferenceThreePointsWithScantLine,
    };
    /*! 
     * \brief simple Math exception class
     */
    class FunctionException : public std::exception {
        public:
            FunctionException ( const std::string & _error ) { m_Error=_error.c_str();}
            ~FunctionException() throw () {} 
            virtual const char * what() const throw() { return m_Error.toStdString().c_str() ; } 
        private:
            QString m_Error ; 
    };
    /*! 
     * \brief base for 3D function range 
     */
    struct Point {
        public :
            Point( double _x, double _y ) { m_x = _x ; m_y = _y ; } 
            double x() const { return m_x ; }
            double y() const { return m_y ; }
        private:
            double m_x ; 
            double m_y ; 
    };
    /*! 
     * Story begins 
     * Class representing functions
     */
    template<class T>
    class IFunction {
        public:
            IFunction () {}
            virtual ~IFunction()  {}
            double                  value ( const T & _ran, int pos = -1 ) const; 
            
            void                    setRange( T * _pRange ) { m_pRange.reset(_pRange) ; }
            const T*                range() const { return m_pRange.get() ;}
            
            void                    setValues( double * _pValues ) { m_pValues.reset(_pValues) ; }
            const double *          values() const { return m_pValues.get() ; } 
            int                     size() const { return m_Size ; } 
            
            void                    setData ( T * _pRange , double * _pValues, int _size ) 
            {
                setRange(_pRange) ; 
                setValues(_pValues);
                m_Size = _size ; 
                m_FunctionType = eDiscrete ; 
            }
            void                    setEquation ( const QString & _eq ) { 
                m_Equation = _eq ; 
                m_FunctionType = eContinuous ; 
                m_Parser.setEquation(_eq); 
                m_NeedToRecalculate = true ; 
            } 
            const QString &         equation () const { return m_Equation ; }
            inline void             setDescription( const QString & _desc ) { m_Description = _desc ;}
            const QString &         description() const { return m_Description ; } 
            void                    setVars ( const QStringList & _list ) { m_Parser.setVars(_list) ;}
            
            void                    recalculateData ( int _size, const T& _rangeMin, const T& _rangeMax, double _step ) ; 
            IFunction<T> *          calculateDerivative( const T & _startX, const T& _stopX, double _step, DerivativeMethod _dMethod = Math::eFiniteDifferenceTwoPoints ) const ; 
            inline bool             needToRecalculate() const { return m_NeedToRecalculate ;}
            
        private:
            boost::scoped_array<T>                      m_pRange ; 
            boost::scoped_array<double>                 m_pValues ;
            int                     m_Size ;
            QList<QString>          m_Variables ;
            QString                 m_Equation ; 
            QString                 m_Description ; 
            FunctionDef             m_FunctionType ; 
            bool                    m_NeedToRecalculate ; 
            class Parser {
                public:
                    Parser( const QString  & _equation="", const QStringList & listOfVars = QStringList()) ;
                    ~Parser() {}
                    double                    evaluate( const T& _x ) const; 
                    inline void               setEquation( const QString & _equation ) {
                        m_pParser->SetExpr( _equation.toStdString() ) ; 
                    };
                    void                     setVars( const QStringList & _vars ) ;
                private:
                    std::auto_ptr<mu::Parser> m_pParser ; 
                    mutable T                 m_fVal ; 
            } m_Parser ;
    };
    // declared specializations !! 
    template<> double Math::IFunction<double>::Parser::evaluate(const double& _x) const ;
    template<> double Math::IFunction<double>::value(const double & _ran, int pos) const ;
    template<> void Math::IFunction<double>::recalculateData ( int _size, const double & _rangeMin, const double & _rangeMax, double _step ) ;
    template<> Math::IFunction<double> * Math::IFunction<double>::calculateDerivative( const double & _startX, const double & _stopX, double _step, DerivativeMethod _dMethod /*= Math::eFiniteDifferenceTwoPoints */) const ;
    
    template<> Math::IFunction<double>::Parser::Parser(const QString& _equation, const QStringList& listOfVars) ;
    template<> void Math::IFunction<double>::Parser::setVars( const QStringList & _vars ) ; 
    ///TODO: move to different file ! 
    
}

#endif // IFUNCTION_H